# Importation des librairies :
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import plotly.express as px
import scipy as sp
from scipy.stats import pearsonr
from scipy.stats import bartlett
from scipy.stats import shapiro
from scipy.stats import chi2_contingency
from scipy.stats import kendalltau, spearmanr
import statsmodels.api as sm
# Importation des données :
cust = pd.read_csv('customers.csv')
prod = pd.read_csv('products.csv')
trans = pd.read_csv('transactions.csv')
cust.head()
| client_id | sex | birth | |
|---|---|---|---|
| 0 | c_4410 | f | 1967 |
| 1 | c_7839 | f | 1975 |
| 2 | c_1699 | f | 1984 |
| 3 | c_5961 | f | 1962 |
| 4 | c_5320 | m | 1943 |
cust.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8623 entries, 0 to 8622 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 client_id 8623 non-null object 1 sex 8623 non-null object 2 birth 8623 non-null int64 dtypes: int64(1), object(2) memory usage: 202.2+ KB
cust.nunique()
client_id 8623 sex 2 birth 76 dtype: int64
prod.head()
| id_prod | price | categ | |
|---|---|---|---|
| 0 | 0_1421 | 19.99 | 0 |
| 1 | 0_1368 | 5.13 | 0 |
| 2 | 0_731 | 17.99 | 0 |
| 3 | 1_587 | 4.99 | 1 |
| 4 | 0_1507 | 3.99 | 0 |
prod.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 3287 entries, 0 to 3286 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 3287 non-null object 1 price 3287 non-null float64 2 categ 3287 non-null int64 dtypes: float64(1), int64(1), object(1) memory usage: 77.2+ KB
prod.nunique()
id_prod 3287 price 1455 categ 3 dtype: int64
prod.categ.value_counts()
0 2309 1 739 2 239 Name: categ, dtype: int64
trans.head()
| id_prod | date | session_id | client_id | |
|---|---|---|---|---|
| 0 | 0_1518 | 2022-05-20 13:21:29.043970 | s_211425 | c_103 |
| 1 | 1_251 | 2022-02-02 07:55:19.149409 | s_158752 | c_8534 |
| 2 | 0_1277 | 2022-06-18 15:44:33.155329 | s_225667 | c_6714 |
| 3 | 2_209 | 2021-06-24 04:19:29.835891 | s_52962 | c_6941 |
| 4 | 0_1509 | 2023-01-11 08:22:08.194479 | s_325227 | c_4232 |
trans.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 679532 entries, 0 to 679531 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679532 non-null object 1 date 679532 non-null object 2 session_id 679532 non-null object 3 client_id 679532 non-null object dtypes: object(4) memory usage: 20.7+ MB
trans.nunique()
id_prod 3267 date 679371 session_id 342316 client_id 8602 dtype: int64
# doublons "transaction"
trans_dup = trans[trans.duplicated()]
trans_dup
| id_prod | date | session_id | client_id | |
|---|---|---|---|---|
| 27778 | T_0 | test_2021-03-01 02:30:02.237437 | s_0 | ct_1 |
| 52424 | T_0 | test_2021-03-01 02:30:02.237419 | s_0 | ct_0 |
| 96687 | T_0 | test_2021-03-01 02:30:02.237412 | s_0 | ct_1 |
| 130188 | T_0 | test_2021-03-01 02:30:02.237419 | s_0 | ct_0 |
| 139339 | T_0 | test_2021-03-01 02:30:02.237443 | s_0 | ct_1 |
| ... | ... | ... | ... | ... |
| 653098 | T_0 | test_2021-03-01 02:30:02.237432 | s_0 | ct_0 |
| 657830 | T_0 | test_2021-03-01 02:30:02.237417 | s_0 | ct_0 |
| 662081 | T_0 | test_2021-03-01 02:30:02.237427 | s_0 | ct_1 |
| 671647 | T_0 | test_2021-03-01 02:30:02.237424 | s_0 | ct_1 |
| 679180 | T_0 | test_2021-03-01 02:30:02.237425 | s_0 | ct_1 |
126 rows × 4 columns
trans = trans.drop_duplicates()
trans.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 679406 entries, 0 to 679531 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679406 non-null object 1 date 679406 non-null object 2 session_id 679406 non-null object 3 client_id 679406 non-null object dtypes: object(4) memory usage: 25.9+ MB
126 doublons supprimés
set(trans.id_prod) - set(prod.id_prod)
{'0_2245'}
trans[trans.id_prod == '0_2245']
| id_prod | date | session_id | client_id | |
|---|---|---|---|---|
| 2633 | 0_2245 | 2022-09-23 07:22:38.636773 | s_272266 | c_4746 |
| 10106 | 0_2245 | 2022-07-23 09:24:14.133889 | s_242482 | c_6713 |
| 11727 | 0_2245 | 2022-12-03 03:26:35.696673 | s_306338 | c_5108 |
| 15675 | 0_2245 | 2021-08-16 11:33:25.481411 | s_76493 | c_1391 |
| 16377 | 0_2245 | 2022-07-16 05:53:01.627491 | s_239078 | c_7954 |
| ... | ... | ... | ... | ... |
| 669730 | 0_2245 | 2021-08-25 09:06:03.504061 | s_80395 | c_131 |
| 670682 | 0_2245 | 2022-03-06 19:59:19.462288 | s_175311 | c_4167 |
| 671286 | 0_2245 | 2022-05-16 11:35:20.319501 | s_209381 | c_4453 |
| 675679 | 0_2245 | 2022-02-11 09:05:43.952857 | s_163405 | c_1098 |
| 677996 | 0_2245 | 2021-12-14 22:34:54.589921 | s_134446 | c_4854 |
221 rows × 4 columns
Le produit référencé "0_2245" et de catégorie 0 est vendu 221 fois sans posséder de données dans la table "produit".
set(prod.id_prod) - set(trans.id_prod)
{'0_1014',
'0_1016',
'0_1025',
'0_1062',
'0_1119',
'0_1318',
'0_1620',
'0_1624',
'0_1645',
'0_1780',
'0_1800',
'0_2308',
'0_299',
'0_310',
'0_322',
'0_510',
'1_0',
'1_394',
'2_72',
'2_86',
'2_87'}
len(set(prod.id_prod) - set(trans.id_prod))
21
21 produits référencés ne font pas partie d'une transaction
trans_prod = pd.merge(trans, prod, on ='id_prod', how = 'left')
trans_prod.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 679406 entries, 0 to 679405 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679406 non-null object 1 date 679406 non-null object 2 session_id 679406 non-null object 3 client_id 679406 non-null object 4 price 679185 non-null float64 5 categ 679185 non-null float64 dtypes: float64(2), object(4) memory usage: 36.3+ MB
data = pd.merge(trans_prod, cust, on ='client_id', how = 'left')
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 679406 entries, 0 to 679405 Data columns (total 8 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id_prod 679406 non-null object 1 date 679406 non-null object 2 session_id 679406 non-null object 3 client_id 679406 non-null object 4 price 679185 non-null float64 5 categ 679185 non-null float64 6 sex 679406 non-null object 7 birth 679406 non-null int64 dtypes: float64(2), int64(1), object(5) memory usage: 46.7+ MB
data.isna().sum()
id_prod 0 date 0 session_id 0 client_id 0 price 221 categ 221 sex 0 birth 0 dtype: int64
data_nan = data[(data.categ.isna()) | (data.price.isna())]
data_nan
| id_prod | date | session_id | client_id | price | categ | sex | birth | |
|---|---|---|---|---|---|---|---|---|
| 2633 | 0_2245 | 2022-09-23 07:22:38.636773 | s_272266 | c_4746 | NaN | NaN | m | 1940 |
| 10106 | 0_2245 | 2022-07-23 09:24:14.133889 | s_242482 | c_6713 | NaN | NaN | f | 1963 |
| 11727 | 0_2245 | 2022-12-03 03:26:35.696673 | s_306338 | c_5108 | NaN | NaN | m | 1978 |
| 15675 | 0_2245 | 2021-08-16 11:33:25.481411 | s_76493 | c_1391 | NaN | NaN | m | 1991 |
| 16377 | 0_2245 | 2022-07-16 05:53:01.627491 | s_239078 | c_7954 | NaN | NaN | m | 1973 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 669606 | 0_2245 | 2021-08-25 09:06:03.504061 | s_80395 | c_131 | NaN | NaN | m | 1981 |
| 670558 | 0_2245 | 2022-03-06 19:59:19.462288 | s_175311 | c_4167 | NaN | NaN | f | 1979 |
| 671162 | 0_2245 | 2022-05-16 11:35:20.319501 | s_209381 | c_4453 | NaN | NaN | m | 1981 |
| 675554 | 0_2245 | 2022-02-11 09:05:43.952857 | s_163405 | c_1098 | NaN | NaN | m | 1986 |
| 677871 | 0_2245 | 2021-12-14 22:34:54.589921 | s_134446 | c_4854 | NaN | NaN | m | 1968 |
221 rows × 8 columns
data_nan.id_prod.value_counts()
0_2245 221 Name: id_prod, dtype: int64
data.categ.value_counts()
0.0 415533 1.0 227169 2.0 36483 Name: categ, dtype: int64
# moyenne des prix de la catégorie "0"
mean_price_categ_0 = round(data[data.categ == 0].price.mean(), 2)
# Remplacement des prixs manquants par la moyenne des prix de la catégorie "0"
data.price = data.price.fillna(mean_price_categ_0)
# Remplacement des catégories manquantes par "0"
data.categ = data.categ.fillna(0)
data[data.id_prod == '0_2245']
| id_prod | date | session_id | client_id | price | categ | sex | birth | |
|---|---|---|---|---|---|---|---|---|
| 2633 | 0_2245 | 2022-09-23 07:22:38.636773 | s_272266 | c_4746 | 10.64 | 0.0 | m | 1940 |
| 10106 | 0_2245 | 2022-07-23 09:24:14.133889 | s_242482 | c_6713 | 10.64 | 0.0 | f | 1963 |
| 11727 | 0_2245 | 2022-12-03 03:26:35.696673 | s_306338 | c_5108 | 10.64 | 0.0 | m | 1978 |
| 15675 | 0_2245 | 2021-08-16 11:33:25.481411 | s_76493 | c_1391 | 10.64 | 0.0 | m | 1991 |
| 16377 | 0_2245 | 2022-07-16 05:53:01.627491 | s_239078 | c_7954 | 10.64 | 0.0 | m | 1973 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 669606 | 0_2245 | 2021-08-25 09:06:03.504061 | s_80395 | c_131 | 10.64 | 0.0 | m | 1981 |
| 670558 | 0_2245 | 2022-03-06 19:59:19.462288 | s_175311 | c_4167 | 10.64 | 0.0 | f | 1979 |
| 671162 | 0_2245 | 2022-05-16 11:35:20.319501 | s_209381 | c_4453 | 10.64 | 0.0 | m | 1981 |
| 675554 | 0_2245 | 2022-02-11 09:05:43.952857 | s_163405 | c_1098 | 10.64 | 0.0 | m | 1986 |
| 677871 | 0_2245 | 2021-12-14 22:34:54.589921 | s_134446 | c_4854 | 10.64 | 0.0 | m | 1968 |
221 rows × 8 columns
data.describe(include = 'all')
| id_prod | date | session_id | client_id | price | categ | sex | birth | |
|---|---|---|---|---|---|---|---|---|
| count | 679406 | 679406 | 679406 | 679406 | 679406.000000 | 679406.000000 | 679406 | 679406.000000 |
| unique | 3267 | 679371 | 342316 | 8602 | NaN | NaN | 2 | NaN |
| top | 1_369 | test_2021-03-01 02:30:02.237449 | s_0 | c_1609 | NaN | NaN | m | NaN |
| freq | 2252 | 2 | 74 | 25488 | NaN | NaN | 340967 | NaN |
| mean | NaN | NaN | NaN | NaN | 17.450547 | 0.441761 | NaN | 1977.813665 |
| std | NaN | NaN | NaN | NaN | 18.326442 | 0.594984 | NaN | 13.575971 |
| min | NaN | NaN | NaN | NaN | -1.000000 | 0.000000 | NaN | 1929.000000 |
| 25% | NaN | NaN | NaN | NaN | 8.870000 | 0.000000 | NaN | 1970.000000 |
| 50% | NaN | NaN | NaN | NaN | 13.990000 | 0.000000 | NaN | 1980.000000 |
| 75% | NaN | NaN | NaN | NaN | 18.990000 | 1.000000 | NaN | 1987.000000 |
| max | NaN | NaN | NaN | NaN | 300.000000 | 2.000000 | NaN | 2004.000000 |
# Visualisation des données incohérantes avec prix négatif et date commençant par "test"
data_inco = data[(data.price < 0) | (data.date.str.startswith('test'))]
data_inco
| id_prod | date | session_id | client_id | price | categ | sex | birth | |
|---|---|---|---|---|---|---|---|---|
| 3019 | T_0 | test_2021-03-01 02:30:02.237419 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| 5138 | T_0 | test_2021-03-01 02:30:02.237425 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| 9668 | T_0 | test_2021-03-01 02:30:02.237437 | s_0 | ct_1 | -1.0 | 0.0 | m | 2001 |
| 10728 | T_0 | test_2021-03-01 02:30:02.237436 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| 15292 | T_0 | test_2021-03-01 02:30:02.237430 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 577222 | T_0 | test_2021-03-01 02:30:02.237424 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| 592959 | T_0 | test_2021-03-01 02:30:02.237422 | s_0 | ct_1 | -1.0 | 0.0 | m | 2001 |
| 607783 | T_0 | test_2021-03-01 02:30:02.237412 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| 625936 | T_0 | test_2021-03-01 02:30:02.237422 | s_0 | ct_0 | -1.0 | 0.0 | f | 2001 |
| 670556 | T_0 | test_2021-03-01 02:30:02.237449 | s_0 | ct_1 | -1.0 | 0.0 | m | 2001 |
74 rows × 8 columns
data_inco.categ.value_counts()
0.0 74 Name: categ, dtype: int64
data_inco.id_prod.value_counts()
T_0 74 Name: id_prod, dtype: int64
# Remplacement des dates qui commencent par "test"
data.loc[data.date.str.startswith("test"), 'date'] = data.loc[data.date.str.startswith("test"), 'date'].str.split('_', expand = True)[1]
# Remplacement des prix négatifs par la moyennes des prix de la catégorie "0"
data.loc[data.price < 0, 'price'] = mean_price_categ_0
# Vérification des résultats
data[data.id_prod == 'T_0']
| id_prod | date | session_id | client_id | price | categ | sex | birth | |
|---|---|---|---|---|---|---|---|---|
| 3019 | T_0 | 2021-03-01 02:30:02.237419 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| 5138 | T_0 | 2021-03-01 02:30:02.237425 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| 9668 | T_0 | 2021-03-01 02:30:02.237437 | s_0 | ct_1 | 10.64 | 0.0 | m | 2001 |
| 10728 | T_0 | 2021-03-01 02:30:02.237436 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| 15292 | T_0 | 2021-03-01 02:30:02.237430 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 577222 | T_0 | 2021-03-01 02:30:02.237424 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| 592959 | T_0 | 2021-03-01 02:30:02.237422 | s_0 | ct_1 | 10.64 | 0.0 | m | 2001 |
| 607783 | T_0 | 2021-03-01 02:30:02.237412 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| 625936 | T_0 | 2021-03-01 02:30:02.237422 | s_0 | ct_0 | 10.64 | 0.0 | f | 2001 |
| 670556 | T_0 | 2021-03-01 02:30:02.237449 | s_0 | ct_1 | 10.64 | 0.0 | m | 2001 |
74 rows × 8 columns
#Formatage des dates:
data['date'] = pd.to_datetime(data['date'], format='%Y/%m/%d %H:%M:%S.%f', errors = 'coerce')
# Vérification des éventuelles erreurs de formatage
data.date.isna().sum()
0
# découpage par jours
data['jour'] = data['date'].dt.strftime('%Y-%m-%d')
data['jour'] = pd.to_datetime(data['jour'])
# découpage par mois
data['mois'] = data.date.dt.to_period('M')
data['mois'] = data['mois'].astype(str)
# découpage en trimestre
# date de début
start_date = data.date.min()
# date de fin
end_date = data.date.max()
# nombre de mois
nbr_mois = pd.date_range(start_date, end_date, freq='M')
# nombre de trimestres
n = int(len(nbr_mois)/3)
# les labels de trimestres
labels = []
for i in range(1,n+1):
labels.append(i)
data['trimestre'] = pd.cut(data.date, n, labels = labels)
# Découpage par années
import datetime
def year(date):
if date < datetime.datetime(2022, 3, 1):
return "Année 1"
elif date >= datetime.datetime(2022, 3, 1):
return "Année 2"
data['annee_exo'] = data['jour'].apply(year)
# Découpage par jours ouvrés et en weekend
data['jour_semaine']= data.date.dt.day_name()
def sem(jour):
if jour in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']:
return 'semaine'
elif jour in ['Saturday', 'Sunday']:
return 'weekend'
data['week'] = data['jour_semaine'].apply(sem)
# age
data['age'] = data.date.dt.year - data.birth
labels_age = [ '< 30 ans', '30 - 50 ans', '> 50 ans']
data['cat_age'] = pd.cut(data.age, bins = [16, 29, 50, 94], labels = labels_age )
data['cat_age'] = data['cat_age'].astype(str)
# Remplacement de 'f' par 'femme' et 'm' par 'homme'
data.sex = data.sex.replace('f', 'femme')
data.sex = data.sex.replace('m', 'homme')
# Remplacement des catégories
data.categ = data.categ.replace(0.0, 'Cat 0')
data.categ = data.categ.replace(1.0, 'Cat 1')
data.categ = data.categ.replace(2.0, 'Cat 2')
data.head()
| id_prod | date | session_id | client_id | price | categ | sex | birth | jour | mois | trimestre | annee_exo | jour_semaine | week | age | cat_age | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0_1518 | 2022-05-20 13:21:29.043970 | s_211425 | c_103 | 4.18 | Cat 0 | femme | 1986 | 2022-05-20 | 2022-05 | 5 | Année 2 | Friday | semaine | 36 | 30 - 50 ans |
| 1 | 1_251 | 2022-02-02 07:55:19.149409 | s_158752 | c_8534 | 15.99 | Cat 1 | homme | 1988 | 2022-02-02 | 2022-02 | 4 | Année 1 | Wednesday | semaine | 34 | 30 - 50 ans |
| 2 | 0_1277 | 2022-06-18 15:44:33.155329 | s_225667 | c_6714 | 7.99 | Cat 0 | femme | 1968 | 2022-06-18 | 2022-06 | 6 | Année 2 | Saturday | weekend | 54 | > 50 ans |
| 3 | 2_209 | 2021-06-24 04:19:29.835891 | s_52962 | c_6941 | 69.99 | Cat 2 | homme | 2000 | 2021-06-24 | 2021-06 | 2 | Année 1 | Thursday | semaine | 21 | < 30 ans |
| 4 | 0_1509 | 2023-01-11 08:22:08.194479 | s_325227 | c_4232 | 4.99 | Cat 0 | homme | 1980 | 2023-01-11 | 2023-01 | 8 | Année 2 | Wednesday | semaine | 43 | 30 - 50 ans |
# fonction de groupage
def grby_sum(df, x, y):
return df.groupby(x)[[y]].sum().reset_index()
def grby_count(df, x, y):
return df.groupby(x)[[y]].count().reset_index()
print('le prix modale est égal à :', data.price.mode()[0], '€')
print('le prix moyen est égal à :', round(data.price.mean(), 2),'€')
print('le prix médian est égal à :', data.price.median(), '€')
# Histogramme
fig = px.histogram(data, x="price", nbins = 30, title = "Distribution des prix")
fig.update_layout(
xaxis_title = "Prix",
yaxis_title = ""
)
fig.show()
le prix modale est égal à : 15.99 € le prix moyen est égal à : 17.45 € le prix médian est égal à : 13.99 €
La majorité des prix se concentrent à gauche et sont inférieurs à 50 €
var_price = round(data.price.var(),3)
print('\nLa variance empirique du prix est égale à :',var_price)# Variance emperique
var_sb_price = round(data.price.var(ddof=0),3)
print('La variance empirique sans biais du prix est égale à :',var_sb_price)# variance empirique sans biais
std_price = round(data.price.std(), 3)
print("L'écart-type empirique du prix est égal à",std_price,' €\n') # écart-type empirique
La variance empirique du prix est égale à : 335.826 La variance empirique sans biais du prix est égale à : 335.826 L'écart-type empirique du prix est égal à 18.326 €
# Représentation de la répartition des prix:
fig = px.box(data, y="price")
fig.update_layout(
title = "Répartition des prix",
yaxis_title = "prix"
)
fig.show()
data1 = data[data.annee_exo == 'Année 1'] # données Année 1
data2 = data[data.annee_exo == 'Année 2'] # données Année 2
ca_total_annee1 = round(data1.price.sum(), 2)
ca_total_annee2 = round(data2.price.sum(), 2)
ca_total = ca_total_annee1 + ca_total_annee2
print("Le chiffre d'affaires total pour la première année est de ", ca_total_annee1, "€")
print("Le chiffre d'affaires total pour la seconde année est de ", ca_total_annee2, "€")
print("Le chiffre d'affaires total pour les deux années est de ", ca_total, "€")
Le chiffre d'affaires total pour la première année est de 5833620.33 € Le chiffre d'affaires total pour la seconde année est de 6023247.15 € Le chiffre d'affaires total pour les deux années est de 11856867.48 €
# CA par jour/mois/trimestre
data.mois = pd.to_datetime(data.mois)
ca_trimestre = grby_sum(data, 'trimestre', 'price')
ca_mois = grby_sum(data, 'mois', 'price')
ca_jours = grby_sum(data, 'jour', 'price')
fig1 = px.line(ca_mois, x="mois", y="price", title = "Evolution mensuelle du chiffre d'affaire")
fig2 = px.line(ca_jours, x="jour", y="price")
fig3 = px.line(ca_trimestre, x="trimestre", y="price")
fig1.update_layout(
xaxis_title = 'mois',
yaxis_title = "Chiffre d'affaire")
fig2.update_layout(
title = "Evolution journalière du chiffre d'affaire",
xaxis_title = 'mois',
yaxis_title = "Chiffre d'affaire")
fig3.update_layout(
title = "Evolution trimestrielle du chiffre d'affaire",
xaxis_title = 'mois',
yaxis_title = "Chiffre d'affaire")
fig2.show()
fig1.show()
fig3.show()
On constate une baisse signficative du chiffre d'affaires au mois d'octobre 2021.
# CA par jour
ca_jours.head()
| jour | price | |
|---|---|---|
| 0 | 2021-03-01 | 17363.22 |
| 1 | 2021-03-02 | 15497.09 |
| 2 | 2021-03-03 | 15198.69 |
| 3 | 2021-03-04 | 15196.07 |
| 4 | 2021-03-05 | 17471.37 |
# Calcul et représentation des moyennes mobiles :
plt.figure(figsize = (25,10))
# Moyennes mobiles sur 30/60/120 jours
rolling_mean1 = ca_jours.rolling(window = 30).mean()
rolling_mean2 = ca_jours.rolling(window = 60).mean()
rolling_mean3 = ca_jours.rolling(window = 120).mean()
# Représentation graphique des moyennes mobiles
plt.plot(ca_jours.price, color = 'blue', label = "Chiffre d'affaire")
plt.plot(rolling_mean1.price, color = 'red', label = "Moyenne mobile 30 jours")
plt.plot(rolling_mean2.price, color = 'green', label = "Moyenne mobile 60 jours")
plt.plot(rolling_mean3.price, color = 'orange', label = "Moyenne mobile 120 jours")
plt.legend(loc = 'best')
plt.show()
On constate que l'évolution du chiffre d'affaire est stationnaire. Nous allons utiliser un test de Dickey-Fuller augmenté (ADF) pour confirmer cette hypothèse.
On définie les hypothèses: </br> H0 : La série est non stationnaire si p-value > 5% </br> H1 : La série est stationnaire si p-value < 5%
#Test de Dickey-Fuller augmenté (ADF)
from statsmodels.tsa.stattools import adfuller
result = adfuller(ca_jours.price)
print('Statistiques ADF : ',format(result[0]))
print('p-value : ',format(result[1]))
print('Valeurs Critiques :')
for key, value in result[4].items():
print('\t{}: {}'.format(key, value))
Statistiques ADF : -4.778714886185212 p-value : 5.9721006639759034e-05 Valeurs Critiques : 1%: -3.4394018678958544 5%: -2.865534780932388 10%: -2.5688972751486325
La P-value est inférieure au seuil de 0.05 et la statistique ADF est inférieur aux valeurs critiques. On rejète donc l'hypothèse nulle H0.</br> ~H0 : La série est non stationnaire si p-value > 5%~ </br> H1 : La série est stationnaire si p-value < 5%
La série qui représente l'évolution du chiffre d'affaire dans le temps est bien stationnaire </br>
# Chiffre d'affaire mensuel par sexes et catégories pour une année entière (Année 2)
data_saison = grby_sum(data2, ['mois', 'sex', 'categ'],'price')
data_saison.head()
| mois | sex | categ | price | |
|---|---|---|---|---|
| 0 | 2022-03 | femme | Cat 0 | 96080.67 |
| 1 | 2022-03 | femme | Cat 1 | 106351.99 |
| 2 | 2022-03 | femme | Cat 2 | 53218.39 |
| 3 | 2022-03 | homme | Cat 0 | 95501.31 |
| 4 | 2022-03 | homme | Cat 1 | 100133.27 |
fig = px.treemap(data_saison, path=[px.Constant("all"), 'mois','sex', 'categ'], values='price')
fig.update_traces(root_color="lightgrey")
fig.update_layout(margin = dict(t=50, l=25, r=25, b=25))
fig.show()
Sur une année entière, on remarque une similitude des surfaces représentants les chiffres d'affaires mensuels en fonction du sex et des catégories. On peut conclure qu'il n'y a pas de saisonnalité.
D =grby_sum(data, ['jour', 'week'], 'price')
D.head()
| jour | week | price | |
|---|---|---|---|
| 0 | 2021-03-01 | semaine | 17363.22 |
| 1 | 2021-03-02 | semaine | 15497.09 |
| 2 | 2021-03-03 | semaine | 15198.69 |
| 3 | 2021-03-04 | semaine | 15196.07 |
| 4 | 2021-03-05 | semaine | 17471.37 |
fig = px.histogram(D, x="jour", y = 'price', color="week",text_auto= '.2s', barnorm = "percent",
title="Ciffre d'affaires mensuel (en proportions jours ouvré - weekend)",
color_discrete_sequence=['#80D03D', '#EBCC4F'])
fig.update_layout(
xaxis_title = 'mois',
yaxis_title = "Chiffre d'affaires en %")
fig.show()
On constate qu'il n'y a pas de saisonnalité en terme de vente entre weekend et jours ouvrés.
# Chiffre d'affaires par mois et par catégorie
ventes_mois_categ = grby_sum(data, ['mois', 'categ'], 'price')
ventes_mois_categ['mois'] = ventes_mois_categ['mois'].astype('str')
ventes_mois_categ.head()
| mois | categ | price | |
|---|---|---|---|
| 0 | 2021-03-01 | Cat 0 | 194512.29 |
| 1 | 2021-03-01 | Cat 1 | 186974.17 |
| 2 | 2021-03-01 | Cat 2 | 101837.27 |
| 3 | 2021-04-01 | Cat 0 | 205371.42 |
| 4 | 2021-04-01 | Cat 1 | 156138.35 |
# Représentation des ventes mensuelles par catégories sur les deux années
plt.figure(figsize = (25,6))
cols = ['#3DD079', '#EBCC4F', '#3DAAD0']
sns.barplot(data = ventes_mois_categ, x = 'mois', y = 'price', hue = 'categ', palette=cols)
plt.title("Evolution des ventes par catégorie", fontweight ="bold")
plt.xticks(rotation=45)
plt.ylabel("Vente en (€)")
plt.show()
On constate une baisse significative des ventes "de catégorie 1" pour le mois d'octobre 2021. Nous allons analyser en détail cette baisse.
# Vente du mois d'octobre
data_oct2021 = data[data.mois == '2021-10']
ventes_oct2021_categ = grby_sum(data_oct2021, ['jour', 'categ'], 'price')
ventes_oct2021_categ.head()
| jour | categ | price | |
|---|---|---|---|
| 0 | 2021-10-01 | Cat 0 | 6950.50 |
| 1 | 2021-10-01 | Cat 1 | 7003.79 |
| 2 | 2021-10-01 | Cat 2 | 3104.05 |
| 3 | 2021-10-02 | Cat 0 | 7141.01 |
| 4 | 2021-10-02 | Cat 2 | 2041.12 |
ventes_oct2021_categ['jour'] = ventes_oct2021_categ['jour'].astype('str')
# Représentation des ventes par catégories du mois d'octobre
plt.figure(figsize = (20,5))
sns.barplot(data = ventes_oct2021_categ, x = 'jour', y = 'price', hue ='categ', palette=cols)
plt.title("Ventes du mois d'octobre par catégorie", fontweight ="bold")
plt.xticks(rotation=45)
plt.xlabel("Jours (Oct)")
plt.ylabel("Vente en (€)")
plt.show()
On constate une abscence des ventes de la "catégorie 1" du 02 au 27 octobre 2021. On peut supposer qu'il y a eu une rupture de stocks ou un problème d'approvisionnement ...etc
# Les 10 premières références en terme de CA
grby_sum(data,'id_prod', 'price').nlargest(10, "price")
| id_prod | price | |
|---|---|---|
| 3097 | 2_159 | 94893.50 |
| 3071 | 2_135 | 69334.95 |
| 3046 | 2_112 | 65407.76 |
| 3035 | 2_102 | 60736.78 |
| 3153 | 2_209 | 56971.86 |
| 2620 | 1_395 | 54356.25 |
| 2592 | 1_369 | 54025.48 |
| 3044 | 2_110 | 53846.25 |
| 3202 | 2_39 | 53060.85 |
| 3105 | 2_166 | 52449.12 |
# Les 10 dérnières références en terme de CA
grby_sum(data,'id_prod', 'price').nsmallest(10, "price")
| id_prod | price | |
|---|---|---|
| 595 | 0_1539 | 0.99 |
| 313 | 0_1284 | 1.38 |
| 719 | 0_1653 | 1.98 |
| 665 | 0_1601 | 1.99 |
| 1785 | 0_541 | 1.99 |
| 2080 | 0_807 | 1.99 |
| 802 | 0_1728 | 2.27 |
| 549 | 0_1498 | 2.48 |
| 2180 | 0_898 | 2.54 |
| 925 | 0_1840 | 2.56 |
df1 = grby_count(data, ['annee_exo','cat_age','sex', 'categ'], 'client_id')
df2 = grby_sum(data, ['annee_exo','cat_age','sex', 'categ'], 'price')
fig2 = px.sunburst(df2, path=['annee_exo', 'sex', 'cat_age', 'categ'], values='price', color ='sex',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig2.update_traces(textinfo = 'label+percent parent')
fig2.update_layout(title_text="Chiffre d'affaires", title_x = 0.5, font_size=12)
fig2.show()
fig1 = px.sunburst(df1, path=['annee_exo','sex', 'cat_age', 'categ'], values='client_id', color ='cat_age',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig1.update_traces(textinfo = 'label+percent parent')
fig1.update_layout(title_text="Volume de vente", title_x = 0.5, font_size=12)
fig1.show()
ca_categ = grby_sum(data, ['categ', 'annee_exo'], 'price')
ca_categ
| categ | annee_exo | price | |
|---|---|---|---|
| 0 | Cat 0 | Année 1 | 2232311.57 |
| 1 | Cat 0 | Année 2 | 2190558.20 |
| 2 | Cat 1 | Année 1 | 2247384.41 |
| 3 | Cat 1 | Année 2 | 2406338.28 |
| 4 | Cat 2 | Année 1 | 1353924.35 |
| 5 | Cat 2 | Année 2 | 1426350.67 |
fig = px.sunburst(ca_categ, path=['annee_exo', 'categ'], values='price', color ='categ',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Chiffre d'affaires par catégorie", title_x = 0.5, font_size=12)
fig.show()
volume_categ = grby_count(data, ['categ', 'annee_exo'], 'client_id')
volume_categ
| categ | annee_exo | client_id | |
|---|---|---|---|
| 0 | Cat 0 | Année 1 | 209839 |
| 1 | Cat 0 | Année 2 | 205915 |
| 2 | Cat 1 | Année 1 | 109735 |
| 3 | Cat 1 | Année 2 | 117434 |
| 4 | Cat 2 | Année 1 | 17788 |
| 5 | Cat 2 | Année 2 | 18695 |
fig = px.sunburst(volume_categ, path=['annee_exo', 'categ'], values='client_id', color ='categ',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Volume des ventes par catégorie", title_x = 0.5, font_size=12)
fig.show()
ca_cat_age = grby_sum(data, ['cat_age', 'annee_exo'], 'price')
ca_cat_age
| cat_age | annee_exo | price | |
|---|---|---|---|
| 0 | 30 - 50 ans | Année 1 | 2832989.80 |
| 1 | 30 - 50 ans | Année 2 | 2899885.70 |
| 2 | < 30 ans | Année 1 | 1584533.62 |
| 3 | < 30 ans | Année 2 | 1561800.94 |
| 4 | > 50 ans | Année 1 | 1416096.91 |
| 5 | > 50 ans | Année 2 | 1561560.51 |
fig = px.sunburst(ca_cat_age, path=['annee_exo', 'cat_age'], values='price', color ='cat_age',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Répartition du chiffre d'affaires par tranche d'age (Année 1-2)", title_x = 0.5, font_size=12)
fig.show()
age_repartition = grby_count(data, ['cat_age', 'annee_exo'], 'client_id')
age_repartition
| cat_age | annee_exo | client_id | |
|---|---|---|---|
| 0 | 30 - 50 ans | Année 1 | 213801 |
| 1 | 30 - 50 ans | Année 2 | 211604 |
| 2 | < 30 ans | Année 1 | 38560 |
| 3 | < 30 ans | Année 2 | 37618 |
| 4 | > 50 ans | Année 1 | 85001 |
| 5 | > 50 ans | Année 2 | 92822 |
fig = px.sunburst(age_repartition, path=['annee_exo', 'cat_age'], values='client_id', color ='cat_age',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Répartition par tranche d'age (Année 1-2)", title_x = 0.5, font_size=12)
fig.show()
ca_sex = grby_sum(data, ['sex', 'annee_exo'], 'price')
ca_sex
| sex | annee_exo | price | |
|---|---|---|---|
| 0 | femme | Année 1 | 2876988.59 |
| 1 | femme | Année 2 | 2984333.10 |
| 2 | homme | Année 1 | 2956631.74 |
| 3 | homme | Année 2 | 3038914.05 |
fig = px.sunburst(ca_sex, path=['annee_exo', 'sex'], values='price', color ='sex',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Répartition du chiffre d'affaire par genre (Année 1-2)", title_x = 0.5, font_size=12)
fig.show()
sex_repartition = grby_count(data, ['sex', 'annee_exo'], 'client_id')
sex_repartition
| sex | annee_exo | client_id | |
|---|---|---|---|
| 0 | femme | Année 1 | 167838 |
| 1 | femme | Année 2 | 170601 |
| 2 | homme | Année 1 | 169524 |
| 3 | homme | Année 2 | 171443 |
fig = px.sunburst(sex_repartition, path=['annee_exo', 'sex'], values='client_id', color ='sex',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Répartition par genre (Année 1-2)", title_x = 0.5, font_size=12)
fig.show()
# CA par client
ca_client = grby_sum(data,'client_id', 'price')
ca_client.nlargest(10, 'price')
| client_id | price | |
|---|---|---|
| 677 | c_1609 | 324033.35 |
| 4388 | c_4958 | 289760.34 |
| 6337 | c_6714 | 153662.76 |
| 2724 | c_3454 | 113669.85 |
| 2513 | c_3263 | 5276.87 |
| 634 | c_1570 | 5271.62 |
| 2108 | c_2899 | 5214.05 |
| 1268 | c_2140 | 5208.82 |
| 7006 | c_7319 | 5155.77 |
| 7791 | c_8026 | 5093.22 |
fig = px.violin(ca_client, y="price", points="all")
fig.update_layout(
title = "Répartition du chiffre d'affaires par client",
#xaxis_title = 'date',
yaxis_title = "Chiffre d'affaires",
#showlegend = True
)
fig.show()
Quatre clients identifiés (c_1609, c_4958, c_6714, c_3454) se distinguent des autres clients par leur grande contribution au chiffre d'affaires généré. On peut supposer qu'ils corespondent à des revendeurs.
Etudions de plus près ces clients qu'on va nommer par la suite "Clients BtoB" et les autres clients "Clients BtoC".
client_BtoB = ['c_1609', 'c_4958', 'c_6714', 'c_3454']
# Données sur les clients BtoB pour chaque année
data1_client_BtoB = data1[data1.client_id.isin(client_BtoB)]
data2_client_BtoB = data2[data2.client_id.isin(client_BtoB)]
# CA des clients BtoB
ca_annee1_BtoB = data1_client_BtoB.price.sum()
ca_annee2_BtoB = data2_client_BtoB.price.sum()
plt.figure(figsize = (15,10))
colors = sns.color_palette(['#80D03D', '#EBCC4F'])
labels = ['Clients BtoB', 'clients BtoC']
plt.subplot(1,2,1)
plt.pie((ca_annee1_BtoB, ca_total_annee1 - ca_annee1_BtoB), labels = labels, colors = colors, autopct = '%1.2f%%')
plt.title("Chiffre d'affaires par type de clients -Année 1-", fontweight ="bold")
plt.subplot(1,2,2)
plt.pie((ca_annee2_BtoB, ca_total_annee2 - ca_annee2_BtoB), labels = labels, colors = colors, autopct = '%1.2f%%')
plt.title("Chiffre d'affaires par type de clients -Année 2-", fontweight ="bold")
plt.show()
Les clients BtoB contribuent à peu près à 7% du chiffre d'affaires total.
data_client_BtoB = data[data.client_id.isin(client_BtoB)]
ff = grby_sum(data_client_BtoB, ['client_id', 'annee_exo', 'categ'], 'price')
fig = px.sunburst(ff, path=['annee_exo', 'client_id', 'categ'], values='price', color ='client_id',
width =600, height = 600, color_discrete_sequence=['#80D03D', '#3DD079', '#3DAAD0', '#EBCC4F'])
fig.update_traces(textinfo = 'label+percent parent')
fig.update_layout(title_text="Répartition du chiffre d'affaires des clients BtoB par catégorie (Année 1-2)", title_x = 0.5, font_size=12)
fig.show()
ca_client_m = data.query('sex == "homme"').groupby('client_id')[['price']].sum().reset_index()
ca_client_f = data.query('sex == "femme"').groupby('client_id')[['price']].sum().reset_index()
# Calcul Lorenz
data_lorenz_m = ca_client_m['price']
data_lorenz_f = ca_client_f['price']
nm = len(data_lorenz_m)
nf = len(data_lorenz_f)
Ym = np.cumsum(np.sort(data_lorenz_m))/ data_lorenz_m.sum()
Yf = np.cumsum(np.sort(data_lorenz_f))/ data_lorenz_f.sum()
Ym = np.append([0],Ym)
Yf = np.append([0],Yf)
Xm = np.linspace(0-1/nm,1+1/nm,nm+1)
Xf = np.linspace(0-1/nf,1+1/nf,nf+1)
# Calcul de l'indice de Gini
AUCm = (Ym.sum() -Ym[-1]/2 -Ym[0]/2)/nm
AUCf = (Yf.sum() -Yf[-1]/2 -Yf[0]/2)/nf
Sm = 0.5 - AUCm
Sf = 0.5 - AUCf
gini_m = 2*Sm
gini_f = 2*Sf
# Courbes de Lorenz
plt.figure(figsize = (15,6))
plt.subplot(1,2,1)
sns.lineplot(Xm, Ym, color = 'r')
sns.lineplot([0,1], [0,1], color = 'b')
plt.title("Répartition du CA (contribution masculine), Gini = %f" % gini_m, fontweight ="bold")
plt.subplot(1,2,2)
sns.lineplot(Xf, Yf, color = 'y')
sns.lineplot([0,1], [0,1], color = 'b')
plt.title("Répartition du CA (contribution féminine), Gini = %f" % gini_f, fontweight ="bold")
plt.show()
Les femmes et les hommes contribuent d'une manière moyennement équitable dans la création du chiffre d'affaires total. On peut dire aussi qu'il y a une similitude dans le comportement d'achat des clients hommes et femmes.
age_ca = grby_sum(data,'age', 'price')
age_ca.head()
| age | price | |
|---|---|---|
| 0 | 17 | 241265.83 |
| 1 | 18 | 384592.43 |
| 2 | 19 | 221910.74 |
| 3 | 20 | 177913.92 |
| 4 | 21 | 172102.39 |
fig = px.scatter(age_ca, x="age", y="price", title = "Montant d'achat en fonction de l'age")
fig.update_layout(
xaxis_title = 'age',
yaxis_title = "Montant d'achat en (€)")
fig.show()
Le lien ici entre le chiffre d'affaires et l'age (deux variables quantitatives) est partiellement linéaire. Nous allons utiliser ici la corrélation de Pearson et de Spearman.
Posons les hypothèses de départ :</br> H0 : Variables indépendantes si p-value > 5% </br> H1 : Variables non indépendantes si p-value < 5%
pearsonr(age_ca.age, age_ca.price)
(-0.856799660216099, 1.459407662056002e-23)
spearmanr(age_ca.age, age_ca.price)
SpearmanrResult(correlation=-0.8720267074697454, pvalue=2.730425001740127e-25)
Dans les deux cas le résultat de la p-value est inférieur à 0.05, on rejète donc l'hypothèse H0:</br> </br> ~H0 : Variables indépendantes si p-value > 5%~ </br> H1 : Variables non indépendantes si p-value < 5% </br></br> On peut dire que l'age et le montant d'achat des clients sont corrélés
age_achat = grby_count(data, 'age', 'client_id')
age_achat.head()
| age | client_id | |
|---|---|---|
| 0 | 17 | 5935 |
| 1 | 18 | 9636 |
| 2 | 19 | 5409 |
| 3 | 20 | 4506 |
| 4 | 21 | 4306 |
fig = px.scatter(age_achat, x="age", y="client_id", title = "Nombre d'achats en fonction de l'age")
fig.update_layout(
xaxis_title = 'age',
yaxis_title = "Nombre d'achats")
fig.show()
Le lien ici entre le nombre d'achats et l'age (deux variables quantitatives) est partiellement linéaire. Nous allons utiliser ici la corrélation Pearson et de Spearman.
Posons les hypothèses de départ :</br> H0 : Variables indépendantes si p-value > 5% </br> H1 : Variables non indépendantes si p-value < 5%
pearsonr(age_achat.age, age_achat.client_id)
(-0.5555773708371885, 1.2935855004108499e-07)
spearmanr(age_achat.age, age_achat.client_id)
SpearmanrResult(correlation=-0.7059143388257313, pvalue=5.275863065515789e-13)
Dans les deux cas le résultat de la p-value est inférieur à 0.05, on rejète donc l'hypothèse H0:</br> </br> ~H0 : Variables indépendantes si p-value > 5%~ </br> H1 : Variables non indépendantes si p-value < 5% </br></br> On peut dire que l'age et le montant d'achat des clients sont corrélés
age_panier = data.groupby('age').agg({'price': ['sum'], 'client_id':['count']}).reset_index()
age_panier['panier_moyen'] = round(age_panier.iloc[:,1]/age_panier.iloc[:,2])
age_panier = age_panier[['age', 'panier_moyen']]
age_panier.head()
| age | panier_moyen | |
|---|---|---|
| 0 | 17 | 41.0 |
| 1 | 18 | 40.0 |
| 2 | 19 | 41.0 |
| 3 | 20 | 39.0 |
| 4 | 21 | 40.0 |
fig = px.scatter(age_panier, x= 'age', y = 'panier_moyen', title = "Panier moyen en fonction de l'age")
fig.update_layout(
xaxis_title = 'age',
yaxis_title = "Panier moyen en (€)")
fig.show()
Il est évident que le panier moyen est en lien avec des tranches d'age bien définies. A savoir, les :</br>
Ici on va utiliser le test d'analyse de variance type ANOVA entre une variable qualitative "catégorie de livres" et quantitative "age des clients".
age_categ = data[['age', 'categ']]
age_categ.head()
| age | categ | |
|---|---|---|
| 0 | 36 | Cat 0 |
| 1 | 34 | Cat 1 |
| 2 | 54 | Cat 0 |
| 3 | 21 | Cat 2 |
| 4 | 43 | Cat 0 |
plt.subplots(figsize=(12,4))
ax = sns.boxplot(y = 'age', x = 'categ', data = age_categ, showmeans= True, palette = cols)
plt.show()
# Calcul des variances
age_categ.groupby("categ")['age'].agg('var')
categ Cat 0 126.094214 Cat 1 240.529843 Cat 2 96.325097 Name: age, dtype: float64
Le test de Bartlett permet de tester si les variances sont significativement différentes ou non. Posons les hypothèses de départ :
H0 : Les variances de chaque groupe sont égales si p-value > 5% </Br> H1 : Les variances de chaque groupe ne sont pas toutes égales < 5%
# test de Bartlett
from scipy.stats import bartlett
bartlett = bartlett(age_categ.age[age_categ.categ == 'Cat 0'],
age_categ.age[age_categ.categ == 'Cat 1'],
age_categ.age[age_categ.categ == 'Cat 2'])
print(" La statistique de bartlett est égale à ", bartlett[0])
print(" La p-value de bartlett est égale à ", bartlett[1])
La statistique de bartlett est égale à 36527.06257330086 La p-value de bartlett est égale à 0.0
On rejète donc l'hypotèse nulle car la p-value est inférieure à 0.05. On peut dire que les variances de chaque groupe ne sont pas toutes égales.
import statsmodels.api as sm
sm.qqplot(age_categ['age'], line = "r")
plt.show()
Le test de Smirnov-Kermogolv permet de tester si les variances sont significativement différentes ou non. Posons les hypothèses de départ :</br></br> H0 : La distribution suit une loi normale si p-value > 5% </br> H1 : La distribution ne suit pas une loi normale si p-value < 5%
# Test de Smirnov-Kermogolov
from scipy import stats
from scipy.stats import kstest
kstest(age_categ['age'], stats.norm.cdf)
KstestResult(statistic=1.0, pvalue=0.0)
On rejète l'hypotèse nulle car la p-value est inférieure à 0.05. On peut dire que la distibution ne suit pas une loi normale.</br></br> ~H0 : La distribution suit une loi normale si p-value > 5%~ </br> H1 : La distribution ne suit pas une loi normale si p-value < 5%
Les hypothèses de test ANOVA ne sont pas réunies. Nous allons donc utiliser le test non paramétrique de Kruskal-Wallis.</br></br> Posons les hypothèses de départ : </br> H0 : Les trois catégories ne différent pas pour l'age si p-value > 5% </br> H1 : Au moins une catégorie différe des autres pour l'age si p-value < 5%
# test non paramétrique de Kruskal
kstat, pval = sp.stats.kruskal(*[group["age"].values for name, group in age_categ.groupby("categ")])
kstat
79491.97844148098
pval
0.0
Le test est significatif, Kstat = 79491.9784 et p-value = 0.0, on peut donc supposer qu'au moins une des 3 catégories diffère des autres pour l'age des clients.
Il s'agit ici de deux variables qualitatives "sexe du client" et "catégorie de livre". On utilisera le test d'indépendance du khi-deux.
# Séléction des données "sexe" et "catégorie":
categ_sex = data[['categ', 'sex']]
categ_sex.head()
| categ | sex | |
|---|---|---|
| 0 | Cat 0 | femme |
| 1 | Cat 1 | homme |
| 2 | Cat 0 | femme |
| 3 | Cat 2 | homme |
| 4 | Cat 0 | homme |
sns.countplot(data = categ_sex, x = 'sex', hue = 'categ', palette = cols)
plt.show()
Posons les hypothèses de départ </br>
H0 : Le sexe des clients et la catégorie de livre sont indépendants si p-value > 5%
H1 : Le sexe des clients et la catégorie de livre sont non indépendants si p-value < 5%
# Matrice de contingence :
categ_sex_cross = pd.crosstab(categ_sex.categ, categ_sex.sex)
categ_sex_cross
| sex | femme | homme |
|---|---|---|
| categ | ||
| Cat 0 | 206257 | 209497 |
| Cat 1 | 114899 | 112270 |
| Cat 2 | 17283 | 19200 |
Visualisons grace à un graphe mosaïque s'il'y a un lien ou une influence entre les deux variables.
from statsmodels.graphics.mosaicplot import mosaic
mosaic(categ_sex, ['categ', 'sex'], title='Mosaïc plot')
plt.show()
Les aires de chaque bloc du graphique sont très différentes, cela indique qu’il y a un lien ou influence entre nos deux variables.
from scipy.stats import chi2_contingency
Khi2_obs, p_value, ddl, effectif_theorique = chi2_contingency(categ_sex_cross)
print( "le résultat du test Khi-2 nous fourni une statistique de", Khi2_obs)
print( "le résultat du test Khi-2 nous fourni une p-value de", p_value)
le résultat du test Khi-2 nous fourni une statistique de 146.99906487909777 le résultat du test Khi-2 nous fourni une p-value de 1.2010432285664067e-32
On rejète donc l'hypotèse nulle car p-value est inférieure au seuil fixé de 5% . On peut dire que le sexe des clients et la catégorie de livre sont non indépendants.</br></br>
H0 : Le sexe des clients et la catégorie de livre sont indépendants si p-value > 5%
H1 : Le sexe des clients et la catégorie de livre sont non indépendants si p-value < 5%
Pour mesurer l’intensité de la liaison entre nos deux variables qualitatives, nous allons calculer le coefficient de "V de Cramer" et le "T Tschuprov" (basés sur les résultats du test Khi-2).
# Test Cramer et de Tschuprov
l = len(categ_sex.categ.unique())
c = len(categ_sex.sex.unique())
N = len(categ_sex)
mini = min((l - 1), (c - 1))
V = np.sqrt(Khi2_obs/(N * mini))
T = np.sqrt(Khi2_obs/(N * ddl))
print("Le résultat de V de Cramer est égal à", V)
print("Le résultat du coefficient de Tschuprov est égal à", T)
Le résultat de V de Cramer est égal à 0.014709320032271354 Le résultat du coefficient de Tschuprov est égal à 0.0104010599414622
Les deux coefficient sont proche de zéro, cela veut dire que la liaison entre le sexe et la catégorie de livre est faible.